using System.Collections.ObjectModel;
using System.Globalization;
using Avalonia;
using Avalonia.Animation;
using Avalonia.Controls;
using Avalonia.Controls.Templates;
using Avalonia.Data.Converters;
using Avalonia.Metadata;
using JMCOM.Components.EpsonRobot.NodeFlow.Command;
using JMCOM.Components.EpsonRobot.NodeFlow.Nodebehind;
using JMCOM.Components.EpsonRobot.NodeFlow.Syntax;
using DelegateCommand = Prism.Commands.DelegateCommand;

namespace JMCOM.Components.EpsonRobot.NodeFlow;

public class NodeEditorViewModel : BindableBase
{
    public NodeEditorViewModel()
    {
        // 测试数据
        
    }
    private static StateMachineViewModel _stateMachineViewModel =
        new StateMachineViewModel();

    public StateMachineViewModel StateMachine
    {
        get => _stateMachineViewModel;
        set => SetProperty(ref _stateMachineViewModel, value);
    }
    
    private StateMachineRunnerViewModel _stateMachineRunnerViewModel =
        new StateMachineRunnerViewModel(_stateMachineViewModel);

    public StateMachineViewModel StateMachineRunner
    {
        get => _stateMachineViewModel;
        set => SetProperty(ref _stateMachineViewModel, value);
    }
 
}

public class NodesTemplateSelector : IDataTemplate
{
    // This Dictionary should store our shapes. We mark this as [Content], so we can directly add elements to it later.
    [Content]
    public Dictionary<Type, IDataTemplate> AvailableTemplates { get; } = new Dictionary<Type, IDataTemplate>();

    // Build the DataTemplate here
    public Control Build(object? param)
    {
        var key = param?.GetType();
        if (key is null) // If the key is null, we throw an ArgumentNullException
        {
            throw new ArgumentNullException(nameof(param));
        }

        return
            AvailableTemplates[key]
                .Build(param); // finally we look up the provided key and let the System build the DataTemplate for us
    }

    // Check if we can accept the provided data
    public bool Match(object? data)
    {
        // Our Keys in the dictionary are strings, so we call .ToString() to get the key to look up
        var key = data?.GetType();
        // var d = data is NodeViewModel; 
        // var e = AvailableTemplates.ContainsKey(key); 
        return data is NodeViewModel && key is not null
                                     // and the key must not be null or empty
                                     && AvailableTemplates
                                         .ContainsKey(key); // and the key must be found in our Dictionary
    }
}

public class ConnectorOffsetConverter : IValueConverter
{
    public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
    {
        double offset = System.Convert.ToDouble(parameter);
        if (value is Size s)
        {
            return new Size((s.Width + offset) / 2, (s.Height + offset) / 2);
        }

        return new Size(offset / 2, offset / 2);
    }

    public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
    {
        double offset = System.Convert.ToDouble(parameter);
        if (value is Size s)
        {
            return new Size((s.Width + offset) / 2, (s.Height + offset) / 2);
        }

        return new Size(offset / 2, offset / 2);
    }
}

public class StateMachineRunnerViewModel : NodifyObservableObject
{
    private StateMachine? _stateMachine;
    private StateNodeViewModel? _activeState;
    private TransitionViewModel? _activeTransition;
    private readonly DebugBlackboardDecorator _debugger = new DebugBlackboardDecorator();
    private readonly Blackboard _original = new Blackboard();
    protected StateMachineViewModel StateMachineViewModel { get; }
    private MachineState _state;

    public MachineState State
    {
        get => _state;
        protected set => SetProperty(ref _state, value);
    }

    private int _nodesVisited;

    public int NodesVisited
    {
        get => _nodesVisited;
        protected set => SetProperty(ref _nodesVisited, value);
    }

    public StateMachineRunnerViewModel(StateMachineViewModel stateMachineViewModel)
    {
        StateMachineViewModel = stateMachineViewModel;
        _debugger.ValueChanged += OnBlackboardKeyValueChanged;
    }

    private void OnBlackboardKeyValueChanged(BlackboardKey key, object? newValue)
    {
        if (_stateMachine != null && _stateMachine.State != MachineState.Stopped)
        {
            var existing =
                StateMachineViewModel.Blackboard.Keys.FirstOrDefault(k => k.Name == key.Name && k.Type == key.Type);
            if (existing != null)
            {
                existing.Value = newValue;
            }
        }
    }

    #region State Machine Actions

    public async void Start()
    {
        NodesVisited = 0;
        _stateMachine = new StateMachine(StateMachineViewModel.States[0].Id, CreateStates(StateMachineViewModel.States),
            CreateBlackboard(StateMachineViewModel.Blackboard));
        _stateMachine.StateTransition += HandleStateTransition;
        _stateMachine.StateChanged += HandleStateChange;
        await _stateMachine.Start();
    }

    public void Stop()
    {
        _stateMachine?.Stop();
        _stateMachine = null;
    }

    private void HandleStateTransition(Guid from, Guid to)
    {
        NodesVisited++;
        SetActiveStateAndTransition(false);
        _activeTransition = StateMachineViewModel.Transitions.FirstOrDefault(t => t.Source.Id == from);
        _activeState = StateMachineViewModel.States.FirstOrDefault(st => st.Id == to);
        SetActiveStateAndTransition(true);
    }

    private void SetActiveStateAndTransition(bool value)
    {
        if (_activeState != null)
        {
            _activeState.IsActive = value;
        }

        if (_activeTransition != null)
        {
            _activeTransition.IsActive = value;
        }
    }

    private void HandleStateChange(MachineState newState)
    {
        if (newState == MachineState.Stopped)
        {
            SetActiveStateAndTransition(false);
            ResetBlackboardToOriginal();
        }

        State = newState;
    }

    private void ResetBlackboardToOriginal()
    {
        var keys = StateMachineViewModel.Blackboard.Keys;
        for (int i = 0; i < keys.Count; i++)
        {
            var key = keys[i];
            key.Value = _original.GetObject(key.Name);
        }
    }

    public void TogglePause()
    {
        if (State == MachineState.Paused)
        {
            _stateMachine?.Unpause();
        }
        else if (State != MachineState.Stopped)
        {
            _stateMachine?.Pause();
        }
    }

    #endregion

    #region Initialize State Machine

    private IEnumerable<State> CreateStates(IEnumerable<StateNodeViewModel> states) =>
        states.Select(s => new DebugStateDecorator(new State(s.Id, CreateTransitions(s), CreateAction(s.Action))));

    private IEnumerable<Transition> CreateTransitions(StateNodeViewModel state)
    {
        var transitions = StateMachineViewModel.Transitions.Where(t => t.Source == state).ToList();
        var result = new List<Transition>(transitions.Count);
        for (int i = 0; i < transitions.Count; i++)
        {
            var transition = transitions[i];
            var tr = new Transition(transition.Source.Id, transition.Target.Id, CreateCondition(transition.Condition));
            result.Add(new DebugTransitionDecorator(tr));
        }

        return result;
    }

    private IBlackboardCondition? CreateCondition(BlackboardItemViewModel? condition)
    {
        if (condition?.Type != null && typeof(IBlackboardCondition).IsAssignableFrom(condition.Type))
        {
            // TODO: DI Container
            var result = (IBlackboardCondition?)Activator.CreateInstance(condition.Type);
            InitializeKeys(condition.Input, result, condition.Type);
            return result;
        }

        return default;
    }

    private IBlackboardAction? CreateAction(BlackboardItemViewModel? action)
    {
        if (action?.Type != null && typeof(IBlackboardAction).IsAssignableFrom(action.Type))
        {
            // TODO: DI Container
            var result = (IBlackboardAction?)Activator.CreateInstance(action.Type);
            InitializeKeys(action.Input, result, action.Type);
            InitializeKeys(action.Output, result, action.Type);
            return result;
        }

        return default;
    }

    private void InitializeKeys(NodifyObservableCollection<BlackboardKeyViewModel> keys, object? instance, Type type)
    {
        for (int i = 0; i < keys.Count; i++)
        {
            var vm = keys[i];
            var key = CreateActionValue(vm);

            // TODO: Property cache
            if (vm.PropertyName != null)
            {
                var prop = type.GetProperty(vm.PropertyName);
                if (prop?.CanWrite ?? false)
                {
                    prop.SetValue(instance, key);
                }
            }
        }
    }

    private Blackboard CreateBlackboard(BlackboardViewModel blackboard)
    {
        Blackboard result = new Blackboard();
        for (int i = 0; i < blackboard.Keys.Count; i++)
        {
            var key = blackboard.Keys[i];
            if (!string.IsNullOrWhiteSpace(key.Name))
            {
                result.Set(new BlackboardKey(key.Name, key.Type), key.Value);
            }
        }

        result.CopyTo(_original);
        _debugger.Attach(result);
        return _debugger;
    }

    private BlackboardProperty CreateActionValue(BlackboardKeyViewModel key)
    {
        if (key.Value is BlackboardKeyViewModel bkv)
        {
            return new BlackboardProperty(new BlackboardKey(bkv.Name, bkv.Type));
        }

        return new BlackboardProperty(key.Value);
    }

    #endregion
}

public class StateMachineViewModel : NodifyObservableObject
{
    public StateMachineViewModel()
    {
        PendingTransition = new TransitionViewModel();
        Runner = new StateMachineRunnerViewModel(this);
        Blackboard = new BlackboardViewModel()
        {
            Actions =
                new NodifyObservableCollection<BlackboardItemReferenceViewModel>(BlackboardDescriptor
                    .GetAvailableItems<IBlackboardAction>()),
            Conditions =
                new NodifyObservableCollection<BlackboardItemReferenceViewModel>(BlackboardDescriptor
                    .GetAvailableItems<IBlackboardCondition>())
        };
        Transitions.WhenAdded(c => c.Source.Transitions.Add(c.Target))
            .WhenRemoved(c => c.Source.Transitions.Remove(c.Target)).WhenCleared(c => c.ForEach(i =>
            {
                i.Source.Transitions.Clear();
                i.Target.Transitions.Clear();
            }));
        States.WhenAdded(x => x.Graph = this).WhenRemoved(x => DisconnectState(x)).WhenCleared(x =>
        {
            Transitions.Clear();
            OnCreateDefaultNodes();
        });
        OnCreateDefaultKeys();
        OnCreateDefaultNodes();
        RenameStateCommand = new RequeryCommand(() => SelectedStates[0].IsRenaming = true,
            () => SelectedStates.Count == 1 && SelectedStates[0].IsEditable);
        DisconnectStateCommand =
            new RequeryCommand<StateNodeViewModel>(x => DisconnectState(x), x => !IsRunning && x.Transitions.Count > 0);
        DisconnectSelectionCommand = new RequeryCommand(() => SelectedStates.ForEach(x => DisconnectState(x)),
            () => !IsRunning && SelectedStates.Count > 0 && Transitions.Count > 0);
        DeleteSelectionCommand = new RequeryCommand(
            () => SelectedStates.ToList().ForEach(x => x.IsEditable.Then(() => States.Remove(x))),
            () => !IsRunning &&
                  (SelectedStates.Count > 1 || (SelectedStates.Count == 1 && SelectedStates[0].IsEditable)));
        AddStateCommand = new RequeryCommand<Point>(
            p => States.Add(new StateNodeViewModel
            {
                Name = "New State",
                IsRenaming = true,
                Location = p,
                ActionReference = Blackboard.Actions.Count > 0 ? Blackboard.Actions[0] : null
            }), p => !IsRunning);
        CreateTransitionCommand = new Command.DelegateCommand<(object Source, object? Target)>(
            s => Transitions.Add(new TransitionViewModel
            {
                Source = (StateNodeViewModel)s.Source, Target = (StateNodeViewModel)s.Target!
            }),
            s => !IsRunning && s.Source is StateNodeViewModel source && s.Target is StateNodeViewModel target &&
                 target != s.Source && target != States[0] && !source.Transitions.Contains(s.Target));
        DeleteTransitionCommand = new RequeryCommand<TransitionViewModel>(t => Transitions.Remove(t), t => !IsRunning);
        RunCommand = new RequeryCommand(() => IsRunning.Then(Runner.Stop).Else(Runner.Start),
            () => Transitions.Count > 0);
        PauseCommand = new RequeryCommand(Runner.TogglePause, () => IsRunning);
    }

    private NodifyObservableCollection<StateNodeViewModel> _states =
        new NodifyObservableCollection<StateNodeViewModel>();

    public NodifyObservableCollection<StateNodeViewModel> States
    {
        get => _states;
        set => SetProperty(ref _states, value);
    }

    private NodifyObservableCollection<StateNodeViewModel> _selectedStates =
        new NodifyObservableCollection<StateNodeViewModel>();

    public NodifyObservableCollection<StateNodeViewModel> SelectedStates
    {
        get => _selectedStates;
        set => SetProperty(ref _selectedStates, value);
    }

    private NodifyObservableCollection<TransitionViewModel> _connections =
        new NodifyObservableCollection<TransitionViewModel>();

    public NodifyObservableCollection<TransitionViewModel> Transitions
    {
        get => _connections;
        set => SetProperty(ref _connections, value);
    }

    private StateNodeViewModel? _selectedState;

    public StateNodeViewModel? SelectedState
    {
        get => _selectedState;
        set => SetProperty(ref _selectedState, value);
    }

    private string? _name = "State Machine";

    public string? Name
    {
        get => _name;
        set => SetProperty(ref _name, value);
    }

    public bool IsRunning => Runner.State != MachineState.Stopped;
    public bool IsPaused => Runner.State == MachineState.Paused;
    public TransitionViewModel PendingTransition { get; }
    public StateMachineRunnerViewModel Runner { get; }
    public BlackboardViewModel Blackboard { get; }
    public INodifyCommand DeleteTransitionCommand { get; }
    public INodifyCommand DeleteSelectionCommand { get; }
    public INodifyCommand DisconnectStateCommand { get; }
    public INodifyCommand DisconnectSelectionCommand { get; }
    public INodifyCommand RenameStateCommand { get; }
    public INodifyCommand AddStateCommand { get; }
    public INodifyCommand CreateTransitionCommand { get; }
    public INodifyCommand RunCommand { get; }
    public INodifyCommand PauseCommand { get; }

    public void DisconnectState(StateNodeViewModel state)
    {
        var transitions = Transitions.Where(t => t.Source == state || t.Target == state).ToList();
        transitions.ForEach(t => Transitions.Remove(t));
    }

    protected virtual void OnCreateDefaultNodes()
    {
        States.Insert(0, new StateNodeViewModel { Name = "Enter", Location = new Point(100, 100), IsEditable = false });
        var currentDelayKey = Blackboard.Keys.First(k => k.Name == "Current Delay");
        var originalDelayKey = Blackboard.Keys.First(k => k.Name == "Original Delay");
        var welcomeKey = Blackboard.Keys.First(k => k.Name == "Welcome");
        States.Add(new StateNodeViewModel
        {
            Name = "Set delay value",
            Location = new Point(300, 100),
            ActionReference = Blackboard.Actions.FirstOrDefault(a => a.Type == typeof(SetKeyValueAction))
        });
        States[1].Action!.Input[0].Value = currentDelayKey;
        States[1].Action!.Input[1].ValueIsKey = false;
        States[1].Action!.Input[1].Type = BlackboardKeyType.Integer;
        States[1].Action!.Input[1].Value = 100;
        States.Add(new StateNodeViewModel
        {
            Name = "Set new delay",
            Location = new Point(380, 250),
            ActionReference = Blackboard.Actions.FirstOrDefault(a => a.Type == typeof(SetStateDelayAction))
        });
        States[2].Action!.Input[0].Value = currentDelayKey;
        States.Add(new StateNodeViewModel
        {
            Name = "Reset delay",
            Location = new Point(300, 350),
            ActionReference = Blackboard.Actions.FirstOrDefault(a => a.Type == typeof(CopyKeyAction))
        });
        States[3].Action!.Input[0].Value = originalDelayKey;
        States[3].Action!.Input[1].Value = currentDelayKey;
        States.Add(new StateNodeViewModel
        {
            Name = "Set original delay",
            Location = new Point(200, 250),
            ActionReference = Blackboard.Actions.FirstOrDefault(a => a.Type == typeof(SetStateDelayAction))
        });
        States[4].Action!.Input[0].Value = originalDelayKey;
        Transitions.Add(new TransitionViewModel
        {
            Source = States[0],
            Target = States[1],
            ConditionReference = Blackboard.Conditions.FirstOrDefault(c => c.Type == typeof(HasKeyCondition))
        });
        Transitions[0].Condition!.Input[0].Value = welcomeKey;
        Transitions.Add(new TransitionViewModel
        {
            Source = States[1],
            Target = States[2],
            ConditionReference = Blackboard.Conditions.FirstOrDefault(c => c.Type == typeof(AreEqualCondition))
        });
        Transitions[1].Condition!.Input[0].Value = welcomeKey;
        Transitions[1].Condition!.Input[1].ValueIsKey = false;
        Transitions[1].Condition!.Input[1].Type = BlackboardKeyType.String;
        Transitions[1].Condition!.Input[1].Value = currentDelayKey.Name;
        Transitions.Add(new TransitionViewModel { Source = States[2], Target = States[3] });
        Transitions.Add(new TransitionViewModel { Source = States[3], Target = States[4] });
        Transitions.Add(new TransitionViewModel { Source = States[4], Target = States[1] });
    }

    protected virtual void OnCreateDefaultKeys()
    {
        Blackboard.Keys.Add(new BlackboardKeyViewModel
        {
            Name = "Current Delay", Type = BlackboardKeyType.Integer, Value = 1000
        });
        Blackboard.Keys.Add(new BlackboardKeyViewModel
        {
            Name = "Original Delay", Type = BlackboardKeyType.Integer, Value = 1000
        });
        Blackboard.Keys.Add(new BlackboardKeyViewModel
        {
            Name = "Welcome", Type = BlackboardKeyType.String, Value = "Current Delay"
        });
    }
}

public class BlackboardViewModel : NodifyObservableObject
{
    private NodifyObservableCollection<BlackboardKeyViewModel> _keys =
        new NodifyObservableCollection<BlackboardKeyViewModel>();

    public NodifyObservableCollection<BlackboardKeyViewModel> Keys
    {
        get => _keys;
        set => SetProperty(ref _keys, value);
    }

    private NodifyObservableCollection<BlackboardItemReferenceViewModel> _actions =
        new NodifyObservableCollection<BlackboardItemReferenceViewModel>();

    public NodifyObservableCollection<BlackboardItemReferenceViewModel> Actions
    {
        get => _actions;
        set => SetProperty(ref _actions, value);
    }

    private NodifyObservableCollection<BlackboardItemReferenceViewModel> _conditions =
        new NodifyObservableCollection<BlackboardItemReferenceViewModel>();

    public NodifyObservableCollection<BlackboardItemReferenceViewModel> Conditions
    {
        get => _conditions;
        set => SetProperty(ref _conditions, value);
    }

    public INodifyCommand AddKeyCommand { get; }
    public INodifyCommand RemoveKeyCommand { get; }

    public BlackboardViewModel()
    {
        AddKeyCommand = new Command.DelegateCommand(() => Keys.Add(new BlackboardKeyViewModel { Name = "New Key " }));
        RemoveKeyCommand = new Command.DelegateCommand<BlackboardKeyViewModel>(key => Keys.Remove(key));
        Keys.WhenAdded(key =>
        {
            var existingKeyNames = Keys.Where(k => k != key).Select(k => k.Name).ToList();
            key.Name = existingKeyNames.GetUnique(key.Name);
        });
    }
}